Skip to content

feat(webapp): dashboard agent — UI - #4529

Open
kathiekiwi wants to merge 28 commits into
feat/dashboard-agent-flowsfrom
feat/dashboard-agent-ui
Open

feat(webapp): dashboard agent — UI#4529
kathiekiwi wants to merge 28 commits into
feat/dashboard-agent-flowsfrom
feat/dashboard-agent-ui

Conversation

@kathiekiwi

@kathiekiwi kathiekiwi commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #4418. Merge that first.

The dashboard agent's UI: the side panel, the marks that tell it which page you're on, and the entry points. #4418 works without this — the system is simply invisible.

Screenshot 2026-08-07 at 15 14 29

What's inside

  • Panel — chat, rich cards, fullscreen, transcript, quota.
  • Suggested prompts — page-aware chips; investigate chips appear only on loader-backed abnormal state.
  • Page markshandle.agentPageContext on 47 routes, ~20 lines each.
  • Entry points — Ask Trigger button, ⌘J, Help & Feedback. The old ⌘I and ?aiHelp= links keep working.

Notes

@changeset-bot

changeset-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 2481567

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ec3630b6-ba7e-4c3e-b450-bede73b6de87

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The pull request expands the Dashboard Agent into a page-aware chat experience. It adds shared channel routing, fullscreen controls, chat history, quotas, transcript handling, structured view blocks, investigations, reports, suggested prompts, and route metadata. It removes selected page-header Docs controls and adds investigation actions for failed runs, waiting runs, and degraded queues. Tests cover routing, prompts, rendering, transcript state, quotas, navigation, accessibility, and report parity.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the dashboard agent UI and includes a screenshot, but it omits the required issue reference, checklist, testing, changelog, and screenshots sections. Add the required template sections, including the issue reference, completed checklist, testing steps, changelog entry, and screenshots section.
Docstring Coverage ⚠️ Warning Docstring coverage is 39.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main dashboard agent UI change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dashboard-agent-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@pkg-pr-new

pkg-pr-new Bot commented Aug 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

@trigger.dev/build

npm i https://pkg.pr.new/@trigger.dev/build@887f5b6

trigger.dev

npm i https://pkg.pr.new/trigger.dev@887f5b6

@trigger.dev/core

npm i https://pkg.pr.new/@trigger.dev/core@887f5b6

@trigger.dev/python

npm i https://pkg.pr.new/@trigger.dev/python@887f5b6

@trigger.dev/react-hooks

npm i https://pkg.pr.new/@trigger.dev/react-hooks@887f5b6

@trigger.dev/redis-worker

npm i https://pkg.pr.new/@trigger.dev/redis-worker@887f5b6

@trigger.dev/rsc

npm i https://pkg.pr.new/@trigger.dev/rsc@887f5b6

@trigger.dev/schema-to-json

npm i https://pkg.pr.new/@trigger.dev/schema-to-json@887f5b6

@trigger.dev/sdk

npm i https://pkg.pr.new/@trigger.dev/sdk@887f5b6

commit: 887f5b6

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@kathiekiwi
kathiekiwi force-pushed the feat/dashboard-agent-ui branch from bd4d4a0 to 887f5b6 Compare August 8, 2026 12:05
coderabbitai[bot]

This comment was marked as resolved.

@kathiekiwi
kathiekiwi force-pushed the feat/dashboard-agent-ui branch from 887f5b6 to 17a0f07 Compare August 8, 2026 14:30
coderabbitai[bot]

This comment was marked as resolved.

@kathiekiwi
kathiekiwi force-pushed the feat/dashboard-agent-ui branch 2 times, most recently from 2460d14 to 9447128 Compare August 8, 2026 18:08
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@kathiekiwi
kathiekiwi force-pushed the feat/dashboard-agent-ui branch from ddb9f3c to 88efcf7 Compare August 9, 2026 00:01
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Observability map

As of 2481567.

Nothing in this pull request moves the report any more. The findings an earlier push reported are gone.

The score and findings here are report-only and never gate the merge. Separately, a required test suite keeps this tool's symbol and route lists in sync with the code they name, and can fail a pull request that renames or removes a symbol they reference, or that adds the first route with a segment they anticipate. Each failure names the list to edit. The rules and their reasons: internal-packages/observability-map/README.md.

devin-ai-integration[bot]

This comment was marked as resolved.

@kathiekiwi
kathiekiwi force-pushed the feat/dashboard-agent-ui branch from 88efcf7 to 83ae7f2 Compare August 9, 2026 08:20

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 new potential issues.

Open in Devin Review

Comment thread apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx Outdated
Comment on lines +19 to +22
vi.mock("~/services/dashboardAgentDb.server", () => ({ dashboardAgentDb: undefined }));

const { sweepDashboardAgentInvestigations } =
await import("~/services/dashboardAgentInvestigationSweep.server");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 New test mocks an application module instead of using a real container, against the repository's testing rule

The new sweep test replaces a real module with a fake one (vi.mock("~/services/dashboardAgentDb.server", …) at apps/webapp/test/dashboardAgentInvestigationSweepCard.test.ts:19), which the repository's testing rules forbid.

Impact: The test suite diverges from the project's stated testing approach, so the guidance stops being reliable for future contributors.

Rules involved

AGENTS.md, "Testing": "We use vitest exclusively. Never mock anything - use testcontainers instead." The test mocks ~/services/dashboardAgentDb.server to avoid opening a pool; the repo's @internal/testcontainers helpers (postgresTest, containerTest) are the sanctioned way to do this.

The mock also forces a dynamic import (const { sweepDashboardAgentInvestigations } = await import("~/services/dashboardAgentInvestigationSweep.server"), lines 21-22), which AGENTS.md's "Imports" section asks to avoid in favour of static imports.

Prompt for agents
`apps/webapp/test/dashboardAgentInvestigationSweepCard.test.ts` uses `vi.mock` on `~/services/dashboardAgentDb.server` purely so that importing `dashboardAgentInvestigationSweep.server` does not open a connection pool, and then has to use `await import(...)` because of the mock ordering. AGENTS.md forbids mocking (use `@internal/testcontainers` instead) and prefers static imports. Since every write in this test is already injected through the `listStale`/`settleAndClose` dependencies, the cleanest fix is to move the pure, dependency-injected `sweepDashboardAgentInvestigations` function into a module that does not import the datastore singleton at all, so the test can import it statically with no mock. Alternatively, run the test against a real Postgres via `postgresTest`/`containerTest`.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mock is dashboardAgentDb: undefined and it stands in for nothing under test — every behavioural dependency in that suite is injected. It exists only to neutralise an import-time side effect: singleton() is eager, so importing the module constructs a Postgres pool. The database half is covered by dashboardAgentInvestigationSweep.test.ts, a real container test with no mocks at all. Removing the stub properly means making the singleton lazy — 26 references across 8 files — which is a refactor well beyond this finding, so it stays with the reason recorded in the test.

Comment thread apps/webapp/app/routes/projects.$projectRef.ai-help.ts
@kathiekiwi
kathiekiwi force-pushed the feat/dashboard-agent-ui branch from 83ae7f2 to 00a22c6 Compare August 9, 2026 09:35

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

@kathiekiwi
kathiekiwi force-pushed the feat/dashboard-agent-ui branch from 5e010b0 to 92efdcd Compare August 9, 2026 12:01
The panel, the page-context marks on the pages the agent reads, and the entry points.
Ask AI (Kapa) owns the two entry points it had before the dashboard agent
replaced it: Cmd-I, and the `?aiHelp=` deep link the CLI's "Get a fix for
this error using AI" line points at. `AskAIRoot` mounts in the `_app`
layout, above every signed-in page, so Cmd-I reaches it from org-level
pages too and the dialog outlives whatever opened it.

The agent no longer reads deep links at all: nothing produced its `?ask=`
param except the CLI redirect, and both readers consume the param, so a
live agent reader would always beat Kapa to it. It stays the fall-through
— where Kapa cannot open (self-hosted, or no website id), both channels
land on the agent instead of dead-ending.
…the pipeline emits

The fixture still set `facts.staleReason`, renamed to `untrustworthyReason` three commits
before the caveat started reading it, so the branch's only trust snapshot fell back to
"could not be verified" for a report whose reason is known.
The scheduled example lost its import line to the standard one, so copying
it gave code that does not compile.
Without org/project/env context the run button rendered but did nothing.
Shortcuts can now ask for the browser default to be prevented, and the
agent's keystroke does.
Selecting a stored chat with no messages dropped you into a fresh draft,
as if the chat had been deleted.
Radix tooltip content is not the accessible name of its trigger, so the
icon-only ask-agent button and the two deploy docs links announced as
unnamed controls. Name them explicitly and pass asChild so the tooltip
trigger stops wrapping them in a second button.

Adds a source scan that fails on the next SimpleTooltip with an unnamed
or double-wrapped control, with the pre-existing sites baselined.
A limit of 0 is zero capacity, not saturation: running >= 0 holds for every
queue, so any backlog marked the queue degraded and offered Investigate, while
the agent's own suggested prompt stayed silent. One predicate now decides it
for the queue detail page, the queues list badge and the page mappers.
Retry appended the last user message again, so the failed turn stayed in the
transcript and its text was sent twice. It now regenerates once the agent has
started answering, and otherwise re-sends the failed turn under its own id.
…e reader's clock

Bar timestamps came from Date.now() during render, so the same bar reported a
different time on every re-render and a server pass disagreed with the client.
They now come from the view model's generatedAt, which the schema already
describes as the timestamp the renderer must not invent.

Moves the arithmetic into report-spark.ts to keep it clock-free and testable,
and drops the unreachable Math.max on the slice end while doing so.
ViewBlocks looked each surviving block's index up with indexOf inside the render
loop: quadratic, and two occurrences of the same block object both answered with
the first index, so they collided on one React key. latestRevisionEntries carries
each survivor's position out instead.
setSearchParams only starts the navigation that drops the param, so a render
before it commits saw the question again and asked it a second time. The reader
now records what it sent and forgets it once the URL no longer carries it, so a
later visit with the same question still works.
A request still in flight at unmount rejected afterwards, and the catch scheduled
a retry that fetched again and set state for a component that was gone. The hook
tracks whether it is still mounted and neither records nor reschedules once it is
not.
The character counter's live region only entered the DOM at the warning point,
and several screen readers only announce updates for a region that was already
there; it is now always mounted and empty until there is something to say.

The history trigger's aria-label replaced the chat title it shows, so a
speech-input user could not activate it by the words on it. The title now leads
the accessible name.
…anel

justify-center on a scrolling column overflows equally in both directions, and
nothing can scroll back past the origin, so at the docked panel's narrowest the
heading and composer were unreachable. The child centres with m-auto, which gives
its space up once there is none to spare.
new URL(environmentPath, origin) ignores origin when the path is absolute, and
the result goes straight into redirect(). Today's only caller passes a
builder-generated internal path, so this closes the gap rather than a hole.
A preview branch named `env` put a second `env` segment in the path and
`lastIndexOf` picked it, shifting every index derived from it.
… keystroke

Between the warning point and the limit the region announced a new count
per character; it now steps in 200s and names the limit on reaching it.
…review fixes

- `_app`: `AskAIRoot` is a sibling of the app, not a wrapper. Its Kapa provider
  mounts client-only, so wrapping the outlet remounted the whole signed-in tree
  once per page load on cloud. Entry points reach it through an open-request
  bridge instead of a render prop.
- The agent no longer hands a `trigger://source/...` target to `navigate`: a
  resolved GitHub URL opens in a new tab, and only a root-relative path is routed.
- Chat history reloads coalesce without answering a request with data fetched
  before it, so a new chat and its title land.
- Help & Feedback and the shortcuts sheet offer each AI surface only where the
  reader has it: Ask AI is back for users without agent access.
- Restore the `AskAgentButton` mount on the deploy blank states, dropped in the
  PR split.
- `agent-shortcuts.test.ts`: import ⌘I from `ask-ai-channels`.
…rface

Where neither Ask AI nor the agent can open, the redirect left `?aiHelp=` on the
environment page with nothing to read it. Send the question to the docs instead.
@kathiekiwi
kathiekiwi force-pushed the feat/dashboard-agent-ui branch from 92efdcd to 2481567 Compare August 9, 2026 12:17

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +165 to +166
const orderRef = useRef(createTranscriptOrder(initialMessages));
const messages = orderTranscript(rawMessages, orderRef.current);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Every message in a chat is re-rendered on each streamed token

The transcript is rebuilt into a brand-new list on every render (orderTranscript(...) at apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx:166), which defeats the caching that was meant to stop already-finished messages from being redrawn while the agent is typing.

Impact: Long conversations get progressively jankier as the agent streams a reply.

Two compounding identity breaks
  1. orderTranscript always returns a freshly-built array (apps/webapp/app/components/dashboard-agent/message-order.ts:35-38), so const stripped = useMemo(() => messages.map(stripStepParts), [messages]) in apps/webapp/app/components/dashboard-agent/DashboardAgentMessages.tsx:295 never hits its cache and re-maps the whole transcript each render.
  2. stripStepParts no longer memoizes per message — the WeakMap cache was removed in this PR (apps/webapp/app/components/dashboard-agent/DashboardAgentMessages.tsx:42-44). Its comment claims "Returns the same reference when there are no step-start parts, so memoization holds", which is true only for messages without step-start parts; any assistant message that does contain one gets a new object identity every render, so the memo-wrapped DashboardAgentTurn re-renders it (including its markdown body) on every streamed token.

Restoring a per-message cache for stripped messages, and/or memoizing the ordered array (e.g. returning the previous array when the ordering and contents are unchanged), would restore the intended behaviour.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant